package com.mvc.app;

import com.mvc.app.exceptions.InvalidModelException;
import com.mvc.app.utils.MVCUtils;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * User: Georgiana.Scarlat
 * Date: 8/29/12
 * Time: 11:06 AM
 */
public class Model {

    private Map<String, Object> beans;
    private Map<String, Object> properties;
    private static final String DELIMITER = "\\.";

    public Model() {
        beans = new HashMap<String, Object>();
        properties = new HashMap<String, Object>();
    }

    public void addBeans(Map<String, Object> beanInstances) {

        this.beans.putAll(beanInstances);
    }

    public void addProperties(Map<String, Object> properties) {

        this.properties.putAll(properties);
    }

    public Map<String, ?> getBeans() {
        return beans;
    }

    public Map<String, ?> getProperties() {
        return properties;
    }


    public Object getBeanByName(String beanName) {
        Object bean;
        if ((bean = beans.get(beanName)) == null)
            throw new InvalidModelException("The model does not have necessary bean " + beanName);
        return bean;
    }


    public Object getPropertyByName(String propertyName) {
        Object property;

        if (propertyName.indexOf('.') > 0)
            return findBeanProperty(propertyName);

        if ((property = properties.get(propertyName)) == null)
            throw new InvalidModelException("The model does not have necessary property " + propertyName);

        return property;
    }


    public boolean isEmpty() {
        return beans.isEmpty() && properties.isEmpty();
    }

    public Object getModelElement(String elementName, Class<? extends Annotation> elementType) {

        if (MVCUtils.BEAN_ANNOTATION_CLASS.isAssignableFrom(elementType))
            return getBeanByName(elementName);
        if (MVCUtils.BEAN_PROPERTY_CLASS.isAssignableFrom(elementType))
            return getPropertyByName(elementName);

        throw new IllegalArgumentException("Model has only beans and properties.");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Model model = (Model) o;

        return !(beans != null ? !beans.equals(model.beans) : model.beans != null) && !(properties != null ? !properties.equals(model.properties) : model.properties != null);

    }

    @Override
    public int hashCode() {
        int result = beans != null ? beans.hashCode() : 0;
        result = 31 * result + (properties != null ? properties.hashCode() : 0);
        return result;
    }


    private Object findBeanProperty(String propertyName) {
        String elements[] = propertyName.split(DELIMITER);

        validateInput(elements);

        return accessBeanPropertyRecursively(elements);
    }

    private Object accessBeanPropertyRecursively(String[] elements) {
        Object result;
        Object bean;
        int index = 0;

        result = getBeanByName(elements[index]);
        while (index < elements.length - 1) {

            bean = result;
            result = MVCUtils.getFiledUsingGetter(bean, elements[++index]);
        }
        return result;
    }

    private void validateInput(String[] elements) {
        if (elements.length < 2)
            throw new IllegalArgumentException("Property name should contain at least one '.' character.");
    }

    @Override
    public String toString() {
        return "Model{" +
                "beans=" + beans +
                ", properties=" + properties +
                '}';
    }
}
